using System.Collections.Generic;
using Istace.Framework.Mapping.Core.Exceptions;
using Istace.Framework.Mapping.Core.Interfaces;

namespace Istace.Framework.Mapping.Core.Internal
{
    /// <summary>
    /// 
    /// </summary>
    /// <typeparam name="T"></typeparam>
    internal sealed class InternalIndexedRepository<T> : IIndexedRepository<T>
    {
        /// <summary>
        /// Default private .ctor
        /// </summary>
        internal InternalIndexedRepository()
        {
            Data = new Dictionary<string, T>();   
        }

        /// <summary>
        /// Default data repository
        /// </summary>
        private readonly Dictionary<string, T> Data;

        /// <summary>
        /// Try to get the object associated with the given index
        /// </summary>
        /// <param name="index">Index key used to identify the object</param>
        /// <param name="output">Object to retreive</param>
        /// <returns>True if the get succeed, Else false</returns>
        public bool TryGetInstance(string index, out T output)
        {
            output = default(T);
            if(Data.ContainsKey(index))
                output = Data[index];

            return output != null;
        }

        /// <summary>
        /// GetInstance an indexed object based on the given key
        /// </summary>
        /// <param name="index">The key used to index the object</param>
        /// <returns>The stored instance</returns>
        public T GetInstance(string index)
        {
            if (this.Data.ContainsKey(index))
                return this.Data[index];
            throw new ObjectNotExistingException<T>(index, this);
        }

        /// <summary>
        /// Try to add an object of T to the data dictionnary indexed on the given key 
        /// </summary>
        /// <param name="key">The key used to index the object within the repository</param>
        /// <param name="objectToAdd">The object to add indexed on key</param>
        /// <returns>True if</returns>
        public bool TryAddInstance(string key, T objectToAdd)
        {
            return TryAddInstance(key, objectToAdd, false);
        }

        /// <summary>
        /// Try to add an object of T to the data dictionnary indexed on the given key 
        /// </summary>
        /// <param name="key">The key used to index the object within the repository</param>
        /// <param name="objectToAdd">The object to add indexed on key</param>
        /// <param name="replaceIfExist">If the object is already indexed, we will replace the existing instance</param>
        /// <returns>True if</returns>
        public bool TryAddInstance(string key, T objectToAdd, bool replaceIfExist)
        {
            if (objectToAdd.GetType() == typeof(T))
            {
                if (this.Data.ContainsKey(key) && replaceIfExist)
                    this.Data[key] = objectToAdd;
                else
                    this.Data.Add(key, objectToAdd);
                return true;
            }
            return false;
        }

        /// <summary>
        /// AddInstance an object to the repository
        /// </summary>
        /// <param name="key">The key used to index the objectToAdd</param>
        /// <param name="objectToAdd">The object to index</param>
        public void AddInstance(string key, T objectToAdd)
        {
            AddInstance(key, objectToAdd, false);
        }

        /// <summary>
        /// AddInstance an object to the repository
        /// </summary>
        /// <param name="key">The key used to index the objectToAdd</param>
        /// <param name="objectToAdd">The object to index</param>
        /// <param name="replaceIfExist">If true, if the object already exist, the existing instance is replaced by the new one</param>
        public void AddInstance(string key, T objectToAdd, bool replaceIfExist)
        {
            if (objectToAdd.GetType() == typeof(T))
            {
                if (!this.Data.ContainsKey(key))
                {
                    this.Data.Add(key, objectToAdd);
                }
                else if (replaceIfExist)
                {
                    this.Data[key] = objectToAdd;
                }
                else
                {
                    throw new MappedObjectAlreadyIndexedException<T>(key, objectToAdd, this.Data[key], this);
                }
            }
            else
            {
                throw new BadTypeRepositoryIndexingException<T>(objectToAdd, key, this);
            }
        }
    }
}